fix(treeview): typed TreeView<T> with viewBuilder; obsolete ContentElement (#447)#452
Closed
codemonkeychris wants to merge 2 commits into
Closed
fix(treeview): typed TreeView<T> with viewBuilder; obsolete ContentElement (#447)#452codemonkeychris wants to merge 2 commits into
codemonkeychris wants to merge 2 commits into
Conversation
…ement (#447) WinUI node-mode TreeView stringifies the node and cannot host a live UIElement in TreeViewNode.Content (verified empirically on native ARM64), so per-node `TreeViewNodeData.ContentElement` always rendered blank. Adds a typed, data-driven `TreeView<T>` (TemplatedTreeViewElement<T>) — the hierarchical peer of ListView<T>: per-node `viewBuilder` (the ItemTemplate equivalent), `childrenSelector` for hierarchy, `keySelector` for identity. OnItemInvoked/OnExpanding hand the developer's own T back. Heterogeneous nodes + per-shape templates fall out of a switch in the viewBuilder. Hosting follows WinUI's own mechanism: each node's view is mounted once and kept on an attached property; the internal TreeViewList's ContainerContentChanging hosts it on realize and releases it (Content=null) on recycle. The host is deferred to a low-priority dispatch and re-validates the container still belongs to the node, so it never perturbs the synchronous expand pass (which otherwise re-prepared the expanding node with a stale IsExpanded and snapped it shut). Expansion is uncontrolled — the selector is only pushed when its value changes, never clobbering user toggles. `TreeViewNodeData.ContentElement` is marked [Obsolete] pointing to TreeView<T>; the legacy path stays functional (warning-suppressed) for back-compat. Samples: DataTemplateDemo §4 migrated to TreeView<T> (heterogeneous pet groups vs. animals); gallery TreeViewPage gains a file-explorer card (folders/docs/images via per-shape templates) and switches source snippets to raw-string literals so they render multi-line. Tests: TemplatedTreeViewFixtures (render, events resolve T, keyed update, expansion-not-clobbered, collapse/expand cycle, expand-collapsed-node, value-type T) + WinUITreeViewNativeProbe documenting native node-mode behavior. Full self-test suite green (1075 fixtures). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… TreeView<T> hosting declarative
Two expand/collapse fixes:
1. Legacy TreeView (the "expand flashes open then shut" regression that
repros on main): DiffTreeViewNodes reset each live node's IsExpanded to
the static TreeViewNodeData value on every reconcile. When a reconcile
fires after the user toggles a node — e.g. an OnExpanding lazy-load that
updates state, or any unrelated state change while nodes are data-expanded
— it snapped the node back to the data's state. Now expansion is treated as
uncontrolled: the data value is only applied when it actually changes
between renders, never clobbering the user's (or WinUI's native) toggle.
2. TreeView<T>: applied the same uncontrolled-expansion rule, and reverted the
experimental ContainerContentChanging hosting back to the declarative
{Binding Content} template. The imperative host/recycle handler perturbed
WinUI's synchronous expand pass (manifested as expand opening then closing,
and child-click collapsing the parent); the binding never perturbs native
expand/collapse.
Tests: legacy + typed expansion-not-clobbered guards, collapse/expand cycle,
expand-collapsed-node, constrained-viewport expand. Full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines
+4151
to
+4153
| foreach (var ln in liveNodes) | ||
| if (GetTreeNodeItem(ln) is { } it) | ||
| existing[o.GetKey(it)] = ln; |
Collaborator
Author
|
Superseded by #453 (follow-up agent). Abandoning this branch. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #447.
Problem
A
TreeViewwhoseTreeViewNodeDatanodes carry aContentElementrenders blank rows — aListViewgiven the same element content renders fine.Verified empirically on native ARM64 (
WinUITreeViewNativeProbe): WinUI's node-modeTreeViewstringifies the node and cannot host a liveUIElementplaced inTreeViewNode.Content(the defaultContentPresenterreceives the node, not its content). Rich per-node visuals in WinUI come from aDataTemplate/ItemTemplate, never a pre-built element. (This is a WPF-ism — thereTreeViewItemis aHeaderedContentControl.)node.Content = Button, no templatenode.Content = Button+ContentControl Content="{Binding Content}"node.Content = "string", no templateFix — typed
TreeView<T>A data-driven, hierarchical peer of
ListView<T>:viewBuilderis the per-node template (WinUIItemTemplateequivalent);childrenSelectorgives hierarchy;keySelectorgives identity.OnItemInvoked/OnExpandinghand the developer's ownTback (resolved via an attached property on the node — duplicate-RCW-safe likeReactorState).switchin theviewBuilder(theItemTemplateSelectorpattern in C#).Hosting (WinUI's own mechanism)
Each node's view is mounted once and kept on an attached property; the internal
TreeViewList'sContainerContentChanginghosts it on realize and releases it (Content = null) on recycle — the only correct way to keep a shared live element from blanking across collapse/expand under virtualization. The host is deferred to a low-priority dispatch and re-validates the container still belongs to the node, so it never perturbs the synchronous expand pass (which otherwise re-prepared the expanding node with a staleIsExpandedand snapped it shut). Expansion is uncontrolled — the selector is only pushed when its value changes, never clobbering user toggles.Deprecation
TreeViewNodeData.ContentElementis[Obsolete]pointing toTreeView<T>; the legacy path stays functional (warning-suppressed) for back-compat. Text-onlyTreeViewNodeDatais unchanged.Samples
DataTemplateDemo§4 migrated toTreeView<T>(heterogeneous pet groups vs. animal leaves).TreeViewPagegains a file-explorer card (folders / docs / images via per-shape templates) and switches source snippets to raw-string literals so they render multi-line.Tests
TemplatedTreeViewFixtures: rich-content render, events resolveT, keyed update reconcile, expansion-not-clobbered, collapse/expand cycle, expand-collapsed-node, value-typeT. PlusWinUITreeViewNativeProbedocumenting native node-mode behavior. Full self-test suite green (1075 fixtures / 4445 assertions, ARM64).🤖 Generated with Claude Code